import { isTracking, trackEffects, triggerEffects } from "./effect";
import { TrackOpTypes, TriggerOpTypes } from "./operations";
import { isArray, hasChanged } from "@vue/shared";
import { isProxy, toRaw, isReactive, toReactive } from "./reactive";
import type { ShallowReactiveMarker } from "./reactive";
import { CollectionTypes } from "./collectionHandlers";
import { createDep, Dep } from "./dep";

/**
 * 看了源码之后可以知道 ref 是 依赖于 reactive 的 两个都是响应式对象
 *  但是可以理解成 ref是一个特殊的响应式对象  是封装了一个 .value的属性
 *   isRef() 方法是根据 传入的对象中是否含有  __v_isRef 来判断，这个就是核心
 */

declare const RefSymbol: unique symbol;

/**
 * 通过 Ref接口可以知道 ref有一个 value 属性
 */
export interface Ref<T = any> {
  value: T;
  /**
   * Type differentiator only.
   * We need this to be in public d.ts but don't want it to show up in IDE
   * autocomplete, so we use a private Symbol instead.
   */
  [RefSymbol]: true;
  /**
   * @internal
   */
  _shallow?: boolean;
}

type RefBase<T> = {
  dep?: Dep;
  value: T;
};

/**
 * 收集 ref响应式对象的依赖
 */
export function trackRefValue(ref: RefBase<any>) {
  if (isTracking()) {
    ref = toRaw(ref);
    if (!ref.dep) {
      ref.dep = createDep();
    }
    /**
     * trackEffects 执行副作用收集 收集 Deps数组
     */
    if (__DEV__) {
      trackEffects(ref.dep, {
        target: ref,
        type: TrackOpTypes.GET,
        key: "value",
      });
    } else {
      trackEffects(ref.dep);
    }
  }
}

export function triggerRefValue(ref: RefBase<any>, newVal?: any) {
  /**
   * 获取ref对象的原始对象 因为 副作用dep和key的映射中的key是根据原始数据进行存的
   */
  ref = toRaw(ref);

  if (ref.dep) {
    if (__DEV__) {
      triggerEffects(ref.dep, {
        target: ref,
        type: TriggerOpTypes.SET,
        key: "value",
        newValue: newVal,
      });
    } else {
      // 触发 triggerEffects 去更新 Deps里面的副作用函数
      triggerEffects(ref.dep);
    }
  }
}

export function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;
export function isRef(r: any): r is Ref {
  // 根据传入的这个对象 是否含有 __v_isRef 这个属性 且这个属性是否为true 来判断是否是ref对象
  return Boolean(r && r.__v_isRef === true);
}

/**
 *
 * @param value ref API 接收一个value参数
 */
export function ref<T extends object>(
  value: T
): [T] extends [Ref] ? T : Ref<UnwrapRef<T>>;
export function ref<T>(value: T): Ref<UnwrapRef<T>>;
export function ref<T = any>(): Ref<T | undefined>;
export function ref(value?: unknown) {
  return createRef(value, false);
}

declare const ShallowRefMarker: unique symbol;

type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };

export function shallowRef<T extends object>(
  value: T
): T extends Ref ? T : ShallowRef<T>;
export function shallowRef<T>(value: T): ShallowRef<T>;
export function shallowRef<T = any>(): ShallowRef<T | undefined>;
export function shallowRef(value?: unknown) {
  return createRef(value, true);
}

/**
 * 创建 ref对象
 */
function createRef(rawValue: unknown, shallow: boolean) {
  // 如果本身是ref对象 那么直接返回即可
  /**
   * Ref对象身上有一个 __v_isRef
   */
  if (isRef(rawValue)) {
    return rawValue;
  }
  return new RefImpl(rawValue, shallow);
}

/**
 * 创建出 Ref对象的类型  --- 这个ts文件的核心代码
 *  ref的核心类
 */
class RefImpl<T> {
  private _value: T;
  private _rawValue: T;

  public dep?: Dep = undefined; // ref对象上有 dep数组
  public readonly __v_isRef = true; // 公开只读的属性  __v_isRef 用于判断一个对象是否是Ref对象

  constructor(value: T, public readonly _shallow: boolean) {
    // toRaw  找到引用的初始数据 _rawValue 存的是原始数据
    this._rawValue = _shallow ? value : toRaw(value);
    // toReactive 转成 Proxy 代理的响应式对象
    this._value = _shallow ? value : toReactive(value);
  }

  get value() {
    trackRefValue(this);
    return this._value;
  }

  set value(newVal) {
    newVal = this._shallow ? newVal : toRaw(newVal);
    if (hasChanged(newVal, this._rawValue)) {
      // 判断两个值是否发生改变 如果发生改变则触发triggerRefValue方法去触发 Deps里面的依赖函数进行更新
      this._rawValue = newVal;
      this._value = this._shallow ? newVal : toReactive(newVal);
      triggerRefValue(this, newVal);
    }
  }
}

export function triggerRef(ref: Ref) {
  triggerRefValue(ref, __DEV__ ? ref.value : void 0);
}

export function unref<T>(ref: T | Ref<T>): T {
  return isRef(ref) ? (ref.value as any) : ref;
}

const shallowUnwrapHandlers: ProxyHandler<any> = {
  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),
  set: (target, key, value, receiver) => {
    const oldValue = target[key];
    if (isRef(oldValue) && !isRef(value)) {
      oldValue.value = value;
      return true;
    } else {
      return Reflect.set(target, key, value, receiver);
    }
  },
};

export function proxyRefs<T extends object>(
  objectWithRefs: T
): ShallowUnwrapRef<T> {
  return isReactive(objectWithRefs)
    ? objectWithRefs
    : new Proxy(objectWithRefs, shallowUnwrapHandlers);
}

type CustomRefFactory<T> = (
  track: () => void,
  trigger: () => void
) => {
  get: () => T;
  set: (value: T) => void;
};

class CustomRefImpl<T> {
  public dep?: Dep = undefined;

  private readonly _get: ReturnType<CustomRefFactory<T>>["get"];
  private readonly _set: ReturnType<CustomRefFactory<T>>["set"];

  public readonly __v_isRef = true;

  constructor(factory: CustomRefFactory<T>) {
    const { get, set } = factory(
      () => trackRefValue(this),
      () => triggerRefValue(this)
    );
    this._get = get;
    this._set = set;
  }

  get value() {
    return this._get();
  }

  set value(newVal) {
    this._set(newVal);
  }
}

export function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {
  return new CustomRefImpl(factory) as any;
}

export type ToRefs<T = any> = {
  // #2687: somehow using ToRef<T[K]> here turns the resulting type into
  // a union of multiple Ref<*> types instead of a single Ref<* | *> type.
  [K in keyof T]: T[K] extends Ref ? T[K] : Ref<UnwrapRef<T[K]>>;
};
export function toRefs<T extends object>(object: T): ToRefs<T> {
  if (__DEV__ && !isProxy(object)) {
    console.warn(
      `toRefs() expects a reactive object but received a plain one.`
    );
  }
  const ret: any = isArray(object) ? new Array(object.length) : {};
  for (const key in object) {
    ret[key] = toRef(object, key);
  }
  return ret;
}

class ObjectRefImpl<T extends object, K extends keyof T> {
  public readonly __v_isRef = true;

  constructor(private readonly _object: T, private readonly _key: K) {}

  get value() {
    return this._object[this._key];
  }

  set value(newVal) {
    this._object[this._key] = newVal;
  }
}

export type ToRef<T> = [T] extends [Ref] ? T : Ref<T>;

export function toRef<T extends object, K extends keyof T>(
  object: T,
  key: K
): ToRef<T[K]> {
  const val = object[key];
  return isRef(val) ? val : (new ObjectRefImpl(object, key) as any);
}

// corner case when use narrows type
// Ex. type RelativePath = string & { __brand: unknown }
// RelativePath extends object -> true
type BaseTypes = string | number | boolean;

/**
 * This is a special exported interface for other packages to declare
 * additional types that should bail out for ref unwrapping. For example
 * \@vue/runtime-dom can declare it like so in its d.ts:
 *
 * ``` ts
 * declare module '@vue/reactivity' {
 *   export interface RefUnwrapBailTypes {
 *     runtimeDOMBailTypes: Node | Window
 *   }
 * }
 * ```
 *
 * Note that api-extractor somehow refuses to include `declare module`
 * augmentations in its generated d.ts, so we have to manually append them
 * to the final generated d.ts in our build process.
 */
export interface RefUnwrapBailTypes {}

export type ShallowUnwrapRef<T> = {
  [K in keyof T]: T[K] extends Ref<infer V>
    ? V
    : // if `V` is `unknown` that means it does not extend `Ref` and is undefined
    T[K] extends Ref<infer V> | undefined
    ? unknown extends V
      ? undefined
      : V | undefined
    : T[K];
};

export type UnwrapRef<T> = T extends ShallowRef<infer V>
  ? V
  : T extends Ref<infer V>
  ? UnwrapRefSimple<V>
  : UnwrapRefSimple<T>;

export type UnwrapRefSimple<T> = T extends
  | Function
  | CollectionTypes
  | BaseTypes
  | Ref
  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]
  ? T
  : T extends Array<any>
  ? { [K in keyof T]: UnwrapRefSimple<T[K]> }
  : T extends object & { [ShallowReactiveMarker]?: never }
  ? {
      [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;
    }
  : T;
